Skip to content

fix(connect): honest, actionable errors across the Atlas connection and graph-init flow#17

Merged
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
connect-flow-clarity
Jul 4, 2026
Merged

fix(connect): honest, actionable errors across the Atlas connection and graph-init flow#17
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
connect-flow-clarity

Conversation

@aayambansal

Copy link
Copy Markdown
Member

Fixes the misleading failure reported on Windows: the workspace's initialize-graph action failed with "Could not initialize the graph. Check openscience connect login and that your account has an active Atlas plan." while openscience connect login said "Already authenticated". Auth state and graph init now tell one consistent, honest story.

End-to-end flow (as mapped in this repo)

  1. openscience connect login (src/cli/cmd/connect.ts) — if a session file exists (<data>/openscience-session.json), prints "Already authenticated" without any network call, then best-effort-syncs. Fresh login: browser loopback (POST /api/v1/auth/cli/browser/start|redeem) or pasted thk_ key (validated via GET /api/cli/balance). saveSession also seeds the bundled Atlas CLI config (~/.config/atlas-cli/config.json) with the same key + base URL.
  2. Provider key / config sync (src/openscience/index.ts syncServices) — GET /api/cli/sync injects managed provider env vars, persists synced-env.json + openscience-synced.json under the XDG config dir (loaded at startup by preload-env.ts). BYOK keys the user set locally are kept: synced values never override values already present, so connecting Atlas on top of local keys stays coherent.
  3. Graph init — workspace InitHero prefills the initialize-atlas-graph skill → agent runs openscience project init --format=json (src/cli/cmd/project.ts) → initProject in src/server/routes/atlas-bridge.ts (same path as the web bridge POST /api/thesis/project/init): pin file → GET /api/agent/projects?dedupe_key=POST /api/agent/projects → fallback POST /api/v1/nodes/commit-new.

The bug: initProject returned string | null and swallowed every error. No session, DNS failure, revoked key (401), no plan (402), and backend 4xx/5xx all collapsed to null, and the CLI printed the one blanket "check login and plan" message. With the managed base defaulting to a dead host, the real failure was DNS — nothing to do with login or plan — while connect login separately reported healthy because it never touches the network when a session file exists and silently ignored the failed sync.

What changed

  • atlas-bridge.ts — new initProjectDetailed() + exported classifyInitFailure(). Failures are classified as:
    • unauthenticated — no session, or the backend answered 401/403 (key revoked);
    • unreachable — network/DNS error or 5xx (the service couldn't be reached);
    • plan — 402, or a plan-coded/worded 4xx (matches the backend's plan_quota_exhausted / collaboration_gated envelopes), with the backend message passed through;
    • backend — any other answer, message passed through verbatim.
      commit-new errors now carry status + body so the fallback path classifies too; a 404 from the newer projects endpoint defers to the proven commit-new fallback's failure. initProject stays as a thin wrapper, and POST /project/init returns the failure fields additively (error, status, message, host) so the SPA keeps working unchanged.
  • project init — fails fast with the connect login hint when there is no managed session (no network call). Text mode prints one actionable message per class, always naming the backend host; on unreachable it explicitly says "You are logged in — this is a network/service issue, not an auth issue." JSON mode includes error/status/message/host. When the Atlas CLI is on PATH, a one-line atlas doctor hint is added.
  • connect login / status / sync — "Already authenticated" and "Connected" now name the backend host they refer to. A failed service sync is never silent anymore: it distinguishes "backend rejected your saved key — re-login" (session was cleared) from "could not sync from — unreachable or plan inactive; provider keys/config were not updated."
  • initialize-atlas-graph skill — the agent now interprets the JSON error kind and relays the matching fix instead of blaming login/plan for a network problem.
  • Tests — classifier coverage (401/403, 402 plan envelope, plan-worded 4xx, 5xx, 4xx pass-through, non-JSON bodies) plus initProjectDetailed unauthenticated fast-fail and no-throw behavior.

Depends on the endpoints fix

DEFAULT_MANAGED_API_BASE on this branch is still the old default host, which does not resolve; that default is being corrected in the separate endpoints/install PR and is intentionally not duplicated here. Until it lands, users on defaults now get the honest message — "Could not reach the Atlas backend at " with the dead host named, which points straight at the root cause — instead of being told to re-login or buy a plan. Once it lands, the same messages reference the correct host, and OPENSCIENCE_API_BASE/SYNSC_API_BASE overrides flow through everywhere (they resolve via src/endpoints.ts).

Backend follow-ups (app.syntheticsciences.ai — not fixable in this repo)

  • Plan-gate graph creation with the standard 402 envelope. If an active plan is required to create projects/root nodes, POST /api/agent/projects and POST /api/v1/nodes/commit-new should answer 402 with the plan_quota_exhausted-style payload (code + user-ready message). The client now passes detail.message through verbatim, so those messages are user-facing.
  • Structured reason on GET /api/cli/sync 402. The CLI can only say "unreachable or plan inactive" for a failed sync with an intact key; a machine-readable reason (mirroring the per-service reason codes already in the sync payload) would let connect login/status say "no active plan" precisely.
  • Alias the legacy default host. Clients already installed with the old default base URL fail DNS before any HTTP. Standing up that hostname as an alias/redirect to the real backend would repair existing installs that never upgrade.
  • Consistent 401 for revoked keys. The client clears the saved session on 401/403 from sync and classifies both as "key rejected"; keeping CLI-facing endpoints on 401 for revoked/expired keys keeps that behavior correct.

Verification

  • bun run typecheck — all 6 packages pass.
  • bun run --cwd backend/cli test — 828 pass, 0 fail (includes the new atlas-bridge tests; the known-flaky snapshot unicode test did not trip on either of two full runs).
  • Exercised every changed path with an isolated env (XDG_* dirs + a fake thk_ session + SYNSC_API_BASE pointed at a dead port or a local stub backend):
    • dead port → JSON {"project_id":null,"error":"unreachable","message":"Unable to connect...","host":"http://127.0.0.1:9"}; text mode says the backend at that host couldn't be reached and that this is not an auth issue;
    • no session → instant {"project_id":null,"error":"unauthenticated",...} / "Not connected to Atlas. Run openscience connect login first." with no network call;
    • stub answering 402 plan_quota_exhaustederror:"plan" with the backend message and the Plan-tab pointer;
    • stub answering 401 → "rejected your saved session (HTTP 401) — Invalid API key. Run openscience connect login...";
    • stub answering 503 → unreachable with status and host;
    • stub 404 on the projects endpoint + 400 on commit-new → the commit-new backend message passes through verbatim (error:"backend", HTTP 400);
    • connect login with a saved session and a dead backend → "Already authenticated (backend: http://127.0.0.1:9)" followed by a loud warning that services could not be synced from that host, instead of silent success; with a 401-answering backend → "rejected your saved key" warning;
    • connect status now prints Backend: <host> and warns when the session is untested against the backend.

…nd graph-init flow

Graph init (openscience project init / initialize-atlas-graph skill) used to
collapse every failure — no session, DNS failure, revoked key, no plan,
backend 4xx/5xx — into one misleading message: 'Check openscience connect
login and that your account has an active Atlas plan'. Meanwhile 'connect
login' printed 'Already authenticated' and swallowed sync failures, so auth
looked healthy while every request failed.

- atlas-bridge: add initProjectDetailed() + classifyInitFailure(), which
  classify init failures as unauthenticated (no session / 401 / 403),
  unreachable (network, DNS, 5xx), plan (402 or plan-coded 4xx, matching the
  backend's plan_quota_exhausted / collaboration_gated envelopes), or backend
  (pass the backend's own message through). commit-new failures now carry
  status + body so the fallback path classifies too. POST /project/init
  returns the failure fields additively.
- project init: fail fast with the connect-login hint when there is no
  managed session (no network call), print one actionable message per
  failure class naming the backend host, include error/status/message/host
  in --format=json output, and note the Atlas CLI when it is on PATH.
- connect login/status/sync: name the backend host on 'Already
  authenticated' and 'Connected', and never swallow a failed service sync —
  distinguish 'backend rejected your saved key' from 'backend unreachable /
  plan inactive' instead of silent success.
- initialize-atlas-graph skill: interpret the JSON error kinds so the agent
  relays the right fix instead of blaming login for a network problem.
- tests: cover the classifier (401/402/plan-worded 4xx/5xx/pass-through) and
  the unauthenticated fast-fail of initProjectDetailed.
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openscience Ready Ready Preview, Comment Jul 4, 2026 6:54pm

Request Review

@aayambansal Aayam Bansal (aayambansal) merged commit c6d8242 into main Jul 4, 2026
9 checks passed
@aayambansal Aayam Bansal (aayambansal) deleted the connect-flow-clarity branch July 4, 2026 18:57
Aayam Bansal (aayambansal) added a commit that referenced this pull request Jul 7, 2026
…g-refresh correctness (#120)

Four provider/transform correctness fixes from the backend audit:

- max reasoning variant no longer starves text to 1 token. On a 32k-output
  Claude, the max thinking budget of cap-1 (31,999) left maxOutputTokens
  returning a single text token; keep it proportional (~3/4 of cap). Large
  caps still clamp at 31,999. (#17)
- Gemini-3 effort variants are nested under thinkingConfig. @ai-sdk/google
  only reads providerOptions.google.thinkingConfig.*, so the old top-level
  keys were dropped and effort selection was silently ignored (always high). (#24)
- Config-model merge falls back to the catalog model's interleaved shape like
  every other capability, so overriding one field (e.g. cost) no longer drops
  the interleaved-reasoning relocation. (#29)
- ModelsDev.refresh() now invalidates provider state so a long-running session
  picks up the hourly-refreshed catalog instead of the first snapshot. (#25)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant